package annotation;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.context.ApplicationContext;
import org.springframework.util.TypeUtils;
import component.PageHolder;
import tools.AnnotationUtil;
import tools.PageUtil;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class PageProxy<T> implements MethodInterceptor {
    private final T target;
    private ApplicationContext applicationContext;

    public PageProxy(T target, ApplicationContext applicationContext) {
        this.target = target;
        this.applicationContext = applicationContext;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        PropertyDescriptor propertyDescriptor = null;
        Object result = null;
        boolean withOptional = false;
        WebDriver driver = applicationContext.getBean(WebDriver.class);

        if (target instanceof IProxyable) {
            if (method.getName().equals("getTarget") && method.getParameterCount() == 0) {
                return target;
            }
        }

        Class<?> tClass = AopUtils.getTargetClass(target);
        PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(tClass);
        for (PropertyDescriptor pd : pds) {
            if (method.equals(pd.getReadMethod())) {
                propertyDescriptor = pd;
            }
        }

        if (propertyDescriptor != null) {
            Field field = null;
            try {
                field = tClass.getDeclaredField(propertyDescriptor.getName());
            }catch (Exception ex){
                return method.invoke(target, args);
            }

            Optional<PageElement> pageElementOptional = AnnotationUtil.getAnnotation(field, PageElement.class);
            Optional<Args> argsOptional = AnnotationUtil.getAnnotation(field, Args.class);

            if (pageElementOptional.isPresent()){
                PageElement pageElement = pageElementOptional.get();
                Type innerType;
                Type propertyType = field.getGenericType();
                if (propertyType instanceof ParameterizedType && TypeUtils.isAssignable(Optional.class, ((ParameterizedType) propertyType).getRawType())) {
                    withOptional = true;
                    innerType = ((ParameterizedType) propertyType).getActualTypeArguments()[0];
                } else {
                    innerType = propertyType;
                }

                if (TypeUtils.isAssignable(WebElement.class, innerType)) {
                    result = PageUtil.findElement(driver, PageHolder.asBy(pageElement.locator()), Duration.ofMillis(pageElement.timeout())).orElse(null);
                }
//                else if (TypeUtils.isAssignable(Table.class, innerType)) {
//                    List<String> tableHeaders = argsOptional
//                            .map(args1 -> AnnotationUtil.<String[]>getArgValue(args1,"headers"))
//                            .map(arr-> Arrays.stream(arr).collect(Collectors.toList()))
//                            .orElse(Collections.emptyList());
//                    int offset = argsOptional.map(args1 -> AnnotationUtil.<Integer>getArgValue(args1,"offset"))
//                            .orElse(0);
//                    result = PageUtil.findElement(driver, PageHolder.asBy(pageElement.locator()), Duration.ofMillis(pageElement.timeout()))
//                            .flatMap(tb -> TableUtil.loadTableData(driver, tb))
//                            .map(tableData->{
//                                if (tableHeaders.size()>0){
//                                    return new Table(tableData,tableHeaders,offset);
//                                }else {
//                                    return new Table(tableData);
//                                }
//                            })
//                            .orElse(null);
//                }
            }

            if (withOptional || result != null) {
                Object finalResult = withOptional ? Optional.ofNullable(result) : result;
                field.setAccessible(true);
                field.set(target, finalResult);
                return finalResult;
            }
        }
        return method.invoke(target, args);
    }
}
